Windows live findings: rescan (WV-2), engine log file (WV-3), bridge-identity probe (WV-4), empty-transport preview gate (WV-5) - #94
Merged
Conversation
…entity, film gate, launcher hint Every change here traces to a finding from the first live Windows hardware validation (WV-2 through WV-5 plus the relaunch race; WV-1 shipped in beta.10): - WV-2: device discovery ran only at engine startup, so a WSL bridge stack that became healthy afterwards left the real scanner invisible until a full app restart. New `scanner.rescan` method: one deliberate re-attempt of the real-backend startup, idempotent, degrading to the sim-only list exactly like startup does, and refused while connected so an active session's backend can never be swapped underneath it. The device bar gains a Rescan button (disabled while connected/busy). PROTOCOL.md documents the method in both copies. - WV-3: a Start-menu launch discards this process's stderr, so every engine diagnostic was lost -- the live forensics had to run on framebuffer screenshots. Engine stderr/error/termination lines now tee to a bounded log (app log dir, 1 MiB, one rotation) that can never break the engine loop. - WV-4: a stale WSL bridge (inherited through a VM clone from a commit window whose driver tree was internally inconsistent) passed bridge-which and bridge-version all session, then refused its first real capture on bundle identity. New `bridge-identity` checker probe: the deployed bridge's two capture-bundle identity files (bundle.py, usb_backend.py) must hash byte-identically to the installed payload's CorrespondingSource copies; red offers the install-bridge-wsl.sh --force redeploy as its fix text. The runbook documents the row (and the runbook-consistency test enforces that documentation). - WV-5: a preview on an empty transport spent minutes in motion-adjacent work and completed with zero frames and no explanation. The engine now probes a fresh status -- the same live path scanner.status uses, never a cached snapshot, so a just-fed roll cannot be falsely refused -- after the preview approval window opens and refuses typed (NO_MEDIA) before roll.preview when film is definitively absent; an undetermined probe proceeds. Placed after the window opens so a rejected overlapping preview still makes zero bridge calls; a refusal retires the window token exactly like a refused roll.preview. - Relaunch race: starting the Hardware Session within seconds of closing the app arms and instantly disarms with no hint (the previous WebView2 teardown still holds its profile; the existing pre-launch process check cannot see it because the old app process is already gone). The launcher now names that likely cause whenever the app exits within ten seconds of starting. Deliberately deferred: WV-6 (the Mac app's manual-placement CTA on REFEED_REQUIRED has no Tauri counterpart) is a parity feature through the motion/binding path, not a defect fix, and stays on the findings ledger. Test changes: mock_bridge's MOCK_BRIDGE_CRASH_ON gains an optional :N occurrence suffix, and its WHILE_PREVIEW_PENDING status hang now tracks a real requested-and-not-established pending flag -- both because the film gate legitimately issues one pre-preview device.status that the old first-occurrence triggers would have eaten. Call-log expectations in the overlap/quarantine/restart tests gained that same leading device.status; their subjects (zero successor bridge calls, fenced reconnects) are unchanged and still asserted. New coverage: film-absent e2e refusal (mutation-proven: disabling the gate fails it with roll.preview reaching the mock), three rescan unit tests, five bridge-identity probe tests, log rotation, and two DeviceBar rescan tests. Suites: engine 25 green suites in each copy (primary and mirror); Tauri crate 85; frontend 61 files, 440 passed, 6 skipped; vendor-sync gate green with the engine-pair fingerprint re-pinned for the identical two-sided edits.
…terlock, sink-before-spawn Adversarial review of the first commit surfaced one blocker and six required fixes; all are addressed here: - The film-presence probe now runs under its own 30-second deadline (PREVIEW_FILM_PROBE_DEADLINE, the eject-deadline idiom): the probe's status read legitimately waits on the driver's adapter-status settle -- up to ~10s draining a post-feed medium-change attention -- while the generic control-plane timeout is 10s and its expiry restarts the bridge and destroys the session. An operator who feeds film and immediately asks to preview must never lose the session to the gate that exists to help them. fresh_status_for_session gained a with-options variant; the zero-argument wrapper keeps the generic bound for every other caller. - The bridge-identity probe now proves the whole driver, not a sample: the deployed interpreter runs the driver's own verify_capture_bundle(require_python_sources=True) self-check over the copy it actually imports (site-packages, not the staged sources), and the imported pin table (bundle.py) must hash byte-identically to the installed payload's copy -- pin-table equality plus self-consistency binds every pinned component. The deploy path honors XDG_DATA_HOME exactly like install-bridge-wsl.sh does, the payload resolves through the Tauri resource directory with the executable's directory as fallback, and a build without the packaged payload reports an honest Unknown instead of a red "reinstall" instruction. - rescanDevices now runs under connectionChangePending, so the whole session store sees a rescan as busy for its full duration (a rescan can hold the engine's single dispatch thread across a cold bridge start), and the device bar no longer clears an unread connection error when an unrelated rescan succeeds. - The engine log sink exists before the spawn attempt and records a sidecar spawn failure -- the most likely "app dies instantly with no diagnostics" case the log exists for -- and the runbook now names the log file's location. spawn_engine's original doc comment is restored to its function (the sink's doc had displaced it). - The Rescan button carries the shared control styling. Suites re-run green end to end: engine both copies, Tauri crate 85, frontend 61 files / 440 passed / 6 skipped, vendor gate with the final engine-pair fingerprint.
…ic log writes Second independent adversarial pass; every remaining finding addressed: - A bridge child that fails its FIRST handshake is now terminated outright in spawn_with_env's error path (restart()'s own terminate_uninitialized_child policy, which the initial spawn never used): such a child owns no device and can have no in-flight USB transaction, so Drop's established-session leave-alive courtesy was the wrong policy -- and with scanner.rescan making failed startups user-repeatable, every impatient click during a slow bridge boot would have orphaned another child contending for the same physical scanner. - scanner.rescan now replaces a real backend whose bridge has died: the set-once field previously kept a dead backend listed-but-unconnectable forever, silently no-oping the exact button an operator would press to fix it. Unhealthy means the child provably exited, so dropping the dead client is safe. - The engine log writes one preformatted buffer per entry with a single write_all (writeln!'s per-fragment writes are not atomic under O_APPEND, and the launcher's own documented relaunch race makes overlapping app instances a real scenario, so mid-line interleaving was reachable), and rotation uses the projected size so a single large entry cannot overshoot the cap. - MOCK_BRIDGE_CRASH_ON now fails loudly on a malformed occurrence suffix instead of silently reinterpreting it as ":1". - The runbook's checker pre-flight lead-in says six ids, matching the list it introduces. Suites: engine 25 green suites in each copy, Tauri crate 85, frontend 61 files / 440 passed / 6 skipped, vendor gate re-pinned for the identical two-sided engine edits.
…ivery GUID The Windows package job went red mid-day between two runs of the same tree: Microsoft rotated the fwlink 2124701 delivery GUID (e4dd9b83... -> eb04ea38...), so Tauri's bundler resolved the new GUID, ignored the pinned copy staged under the old path, downloaded a fresh installer, and the pinned-tool verifier failed closed on the unexpected directory -- the release-toolchain lockdown catching real upstream drift exactly as designed. The replacement artifact was re-verified before re-pinning: the official fwlink redirect resolves to the new GUID, and the downloaded installer hashes 6ac57a21414742ac1a6a03bf9516a048897317cef04a49967b283093e29c31b7 at 212,668,624 bytes. Both pin sites (the installer script and build-and-verify.ps1) move together; the installer-suite tests stay green.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this fixes
Every finding from the first live Windows hardware validation except WV-1 (already shipped in beta.10) and WV-6 (deliberately deferred, see below):
scanner.rescanengine method (idempotent, degrades to sim-only like startup, refused while connected) + a Rescan button in the device bar + PROTOCOL.md coverage in both copies.bridge-which/bridge-versionall session and then refused its first real capture on bundle identity. Newbridge-identitychecker probe hashes the deployed bridge's two capture-bundle identity files against the installed payload's CorrespondingSource copies; red offers theinstall-bridge-wsl.sh --forceredeploy. Runbook documents the row; the runbook-consistency test enforces it.NO_MEDIAbeforeroll.previewwhen film is definitively absent; undetermined proceeds. A refusal retires the window token exactly like a refusedroll.preview, and a rejected overlapping preview still makes zero bridge calls.Deferred: WV-6 (Tauri counterpart of the Mac manual-placement CTA on
REFEED_REQUIRED) — a parity feature through the motion/binding path, not a defect fix; it stays on the findings ledger rather than being rushed here.Verification
roll.previewreaching the mock's call log).MOCK_BRIDGE_CRASH_ONgains an optional:Noccurrence suffix and the WHILE_PREVIEW_PENDING hang now tracks a real requested-not-established flag, because the gate legitimately issues one pre-previewdevice.status. The overlap/quarantine/restart tests' call-log pins gained that same leading entry; their actual subjects (zero successor bridge calls, fenced reconnects) are unchanged and still asserted.Rounds 2 and 3 (two independent adversarial reviews)
Round 2 addressed the first review's blocker and required items: the film-presence probe runs under its own 30s deadline (
PREVIEW_FILM_PROBE_DEADLINE) so the driver's legitimate ~10s adapter-status settle can never trip the generic 10s timeout whose expiry destroys the session; thebridge-identityprobe now proves the whole driver (the deployed interpreter runs the driver's own all-components capture-bundle self-check on the copy it imports, plus a pin-table hash binding to the installed payload), honorsXDG_DATA_HOME, resolves the payload through the Tauri resource dir with exe-dir fallback, and reports Unknown (not red) on dev/portable builds;rescanDevicesruns underconnectionChangePending; the log sink exists before the spawn attempt and records spawn failures; the runbook documents the log path;spawn_engine's displaced doc comment is restored.Round 3 addressed the second review's remaining findings: a bridge child that fails its first handshake is terminated outright (the restart path's own
terminate_uninitialized_childpolicy, never applied to the initial spawn) so repeated rescans during a slow bridge boot cannot orphan children contending for the scanner; rescan replaces a real backend whose bridge died (previously set-once, listed-but-dead forever); log entries are single atomicwrite_alls with projected-size rotation (mid-line interleaving across overlapping app instances was reachable, and a large entry could overshoot the cap);MOCK_BRIDGE_CRASH_ONfails loudly on malformed occurrence suffixes; the runbook's "five ids" lead-in says six.Deliberately deferred, named by reviewers and accepted: rescan blocking the single dispatch thread during a cold bridge start (mitigated by the store-level busy interlock), the
PROBE_IDSconstant/test/runbook triplication (pre-existing), and epoch-seconds log timestamps.